package dllloader

/* #include <stdio.h>
#include <stdlib.h>
extern void * MemoryLoadLibrary(const void *);
extern void * MemoryGetProcAddress(void *, char *);
extern void MemoryFreeLibrary(void *);
*/
import "C"
import (
	"errors"
	"syscall"
	"unsafe"
)

type Handle uintptr

// A MemoryDLL implements access to a single MemoryDLL.
type MemoryDLL struct {
	Name   string
	handle Handle
}

// DLLError describes reasons for DLL load failures.
type DLLError struct {
	Err     error
	ObjName string
	Msg     string
}

func (e *DLLError) Error() string { return e.Msg }

func (e *MemoryDLL) Handle() Handle {
	return e.handle
}

// FindProc searches DLL d for procedure named name and returns *Proc
// if found. It returns an error if search fails.
func (d *MemoryDLL) FindProc(name string) (proc *Proc, err error) {
	return memoryGetProcAddress(d, name)
}

// MustFindProc is like FindProc but panics if search fails.
func (d *MemoryDLL) MustFindProc(name string) *Proc {
	p, e := d.FindProc(name)
	if e != nil {
		panic(e)
	}
	return p
}
func (d *MemoryDLL) NewProc(name string) *Proc {
	p, _ := d.FindProc(name)
	/* 	if e != nil {
		panic(e)
	} */
	return p
}

// Release unloads DLL d from memory.
func (d *MemoryDLL) Release() error {
	memoryFreeLibrary(d)
	return nil
}

// A Proc implements access to a procedure inside a DLL.
type Proc struct {
	Dll  *MemoryDLL
	Name string
	addr uintptr
}

// Addr returns the address of the procedure represented by p.
// The return value can be passed to Syscall to run the procedure.
func (p *Proc) Addr() uintptr {
	return p.addr
}

// Call executes procedure p with arguments a. It will panic, if more then 15 arguments
// are supplied.
//
// The returned error is always non-nil, constructed from the result of GetLastError.
// Callers must inspect the primary return value to decide whether an error occurred
// (according to the semantics of the specific function being called) before consulting
// the error. The error will be guaranteed to contain syscall.Errno.
func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
	return syscall.SyscallN(p.Addr(), a...)
}

// remembe to release when this dll is useless
func NewMemoryDLL(dlldata []byte, dllname string) (*MemoryDLL, error) {
	dlldataPointer := unsafe.Pointer(&dlldata[0])
	handle := C.MemoryLoadLibrary(dlldataPointer)
	if handle != nil {
		return &MemoryDLL{
			Name:   dllname,
			handle: Handle(handle),
		}, nil
	} else {
		e := errors.New("dll data error")
		return nil, &DLLError{
			Err:     e,
			ObjName: dllname,
			Msg:     "Failed to load " + dllname + ": " + e.Error(),
		}
	}

}

func memoryGetProcAddress(dll *MemoryDLL, procname string) (proc *Proc, err error) {
	cname := C.CString(procname)
	defer C.free(unsafe.Pointer(cname))
	addr := C.MemoryGetProcAddress(unsafe.Pointer(dll.handle), cname)
	if addr != nil {
		return &Proc{
			Dll:  dll,
			Name: procname,
			addr: uintptr(addr),
		}, nil
	}
	e := errors.New("no such function")
	return nil, &DLLError{
		Err:     e,
		ObjName: procname,
		Msg:     "Failed to find " + procname + " procedure in " + dll.Name + ": " + e.Error(),
	}
}

// remember free!
func memoryFreeLibrary(dll *MemoryDLL) {
	C.MemoryFreeLibrary(unsafe.Pointer(dll.handle))
}
